package com.atguigu.gmall.index.config;

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.ResultType;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * Created by IntelliJ IDEA.
 *
 * @Author: 86182
 * @Date: 2021/8/12
 * @Time: 17:15
 * @Version:
 */
@Aspect
@Component
public class GmallCacheAspect {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RBloomFilter bloomFilter;

    // 1.通知方法必须有Object返回值
    // 2.必须要有一个ProceedingJoinPoint类型的参数
    // 3.必须抛出一个Throwable类型的异常
    // 4.必须手动执行目标方法: joinPoint.proceed(joinPoint.getArgs());

    //execution(* com.atguigu.gmall.index.service.*.*(..))
    @Around("@annotation(com.atguigu.gmall.index.config.GmallCache)")
    public Object around(ProceedingJoinPoint joinPoint)throws Throwable{
        // 获取切点方法的签名(强转成MethodSignature)
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取方法对象
        Method method = signature.getMethod();
        //获取方法上的GmallCache注解的对象
        GmallCache gmallCache = method.getAnnotation(GmallCache.class);
        // 获取注解中的缓存前缀
        String prefix = gmallCache.prefix();
        // 获取方法的参数列表
        String args = StringUtils.join(joinPoint.getArgs(), ",");
        //组装缓存的key
        String key = prefix + args;

        // 使用布隆过滤器,判定数据是否存在,如果不存在,直接返回空
        if (!this.bloomFilter.contains(key)){
            return null;
        }

            //1.查询缓存,如果缓存命中,直接返回
            String json = this.redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(json)){
                return JSON.parseObject(json,method.getReturnType());
            }
            // 2. 为了防止缓存击穿,添加分布式锁
            RLock fairLock = this.redissonClient.getFairLock(gmallCache.lock() + args);
            fairLock.lock();

        try {
            //3. 再次查询缓存,获取锁的过程中,可以有其他请求把数据放入缓存
            String json2 = this.redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(json2)){
                return JSON.parseObject(json2,method.getReturnType());
            }
            //4.执行目标方法,获取数据
            Object result = joinPoint.proceed(joinPoint.getArgs());
            //5.放入缓存,并释放分布式锁
            if (result != null){
                //解决缓存雪崩
                int timeout = gmallCache.timeout() + new Random().nextInt(gmallCache.random());
                this.redisTemplate.opsForValue().set(key,JSON.toJSONString(result),timeout, TimeUnit.MINUTES);
            }
            return result;
        } finally {
            fairLock.unlock();
        }

    }
}
